﻿define(function (require) {
    var system = require('durandal/system');
    var logger = require('modules/logger');
    var serviceName = 'api/data';
    var manager = new breeze.EntityManager({ serviceName: "api/data", saveOptions: new breeze.SaveOptions({ allowConcurrentSaves: true }) });
    var isSaving = false;

    var datacontext = {
        getOrders: getOrders,
        createOrder: createOrder,
        createOrderLine: createOrderLine,
        getOrder: getOrder,
        getOrdersByCustomer: getOrdersByCustomer,
        getCustomers: getCustomers,
        getCustomer: getCustomer,
        getItems: getItems,
        addEntity: addEntity,
        extendOrder: extendOrder,
        saveChanges: saveChanges
    };

    //#region orders

    function getOrders(includeClosed, ordersObservable) {
        var query = breeze.EntityQuery
                .from("Orders")
                .where("IsActive", "==", true)
                .orderBy("CreationDate desc");

        if (!includeClosed) {
            query = query.where("IsPending", "==", true);
        }

        return manager.executeQuery(query).then(function (data) {
            extendOrders(data, ordersObservable);
        }).fail(queryFailed);
    }

    function getOrdersByCustomer(customerId, ordersObservable) {
        var query = breeze.EntityQuery
                .from("Orders")
                .where("IsActive", "==", true)
                .where("CustomerId", "==", customerId)
                .orderBy("CreationDate desc");
        return manager.executeQuery(query).then(function (data) {
            extendOrders(data, ordersObservable);
            return true;
        }).fail(queryFailed);
    }

    function getOrder(id, extendFunction) {
        var query = breeze.EntityQuery
               .from("Orders")
               .where("Id", "==", id)
               .where("IsActive", "==", true);

        return manager.executeQuery(query).then(function (data) {
            var order = data.results[0];
            if (order) {
                extendOrder(order);
                extendFunction(order);
                return order;
            }
            logger.error("Order not found");
            return false;
        }).fail(queryFailed);
    }

    function createOrder(initialValues) {
        var order = manager.createEntity('Order', initialValues);
        return order;
    }

    function createOrderLine(initialValues) {
        return manager.createEntity('OrderLine', initialValues);
    }

    function extendOrders(data, ordersObservable) {
        ordersObservable.removeAll();
        data.results.forEach(function (order) {
            extendOrder(order);
            ordersObservable.push(order);
        });
    }

    function extendOrder(order) {
        extendCustomer(order.Customer());
        order.TotalAmount = ko.computed(function () {
            var totalAmount = 0;
            $.each(order.OrderLines(), function (ind, orderLine) {
                if (orderLine && orderLine.Item())
                    totalAmount = totalAmount + (orderLine.Quantity() * orderLine.Item().Price());
            });
            return totalAmount;
        });

        order.entityAspect.propertyChanged.subscribe(function () {
            if (order.propertyChangedPending) { return; }
            order.propertyChangedPending = true;
            setTimeout(validateAndSaveModifiedOrder, 10);

            function validateAndSaveModifiedOrder() {
                if (order.entityAspect.entityState.isModified()) {
                    if (order.entityAspect.validateEntity()) {
                        order.ModifiedDate(new Date());
                        datacontext.saveChanges();
                    } else {
                        handleItemErrors(order);
                    }
                }
                order.propertyChangedPending = false;
            }
        });
    }

    //#endregion orders

    //#region customers

    function getCustomers(customersObservable, extendFunction) {
        var query = breeze.EntityQuery
                .from("Customers")
                .orderBy("Name");

        return manager.executeQuery(query).then(function (data) {
            customersObservable.removeAll();
            data.results.forEach(function (customer) {
                extendCustomer(customer);
                customersObservable.push(customer);
            });
        }).then(extendFunction).fail(queryFailed);
    }

    function extendCustomer(customer) {
        customer.FullName = ko.computed(function () {
            return customer.Name() + ' ' + customer.Surname();
        });
    }

    function getCustomer(id, customerObservable) {
        var query = breeze.EntityQuery
                .from("Customers")
                 .where("Id", "==", id);

        return manager.executeQuery(query).then(function (data) {
            var customer = data.results[0];
            if (customer) {
                customerObservable(customer);
                return true;
            }
            logger.error("Customer not found");
            return false;
        }).fail(queryFailed);
    }

    //#endregion customers

    //#region items

    function getItems() {
        var query = breeze.EntityQuery
                .from("Items")
                .orderBy("ItemCode");
        return manager.executeQuery(query);
    }

    //#endregion items

    //#region generic

    function addEntity(entity) {
        manager.addEntity(entity);
    }

    function handleItemErrors(item) {
        if (!item) { return; }
        var errs = item.entityAspect.getValidationErrors();
        if (errs.length == 0) {
            logger.info("No errors for current item");
            return;
        }
        var firstErr = item.entityAspect.getValidationErrors()[0];
        logger.error(firstErr.errorMessage);
        item.entityAspect.rejectChanges();
    }

    function queryFailed(error) {
        logger.error(error.message, "Query failed");
    }

    function saveChanges(onSave) {
        if (manager.hasChanges()) {
            if (isSaving) {
                setTimeout(saveChanges, 50);
                return;
            }
            isSaving = true;
            manager.saveChanges()
                .then(saveSucceeded)
                .then(onSave)
                .fail(saveFailed)
                .fin(saveFinished);
        }
    }

    function saveSucceeded(saveResult) {
        logger.success("Saved");
        logger.log(saveResult);
    }

    function saveFailed(error) {
        var reason = error.message;
        var detail = error.detail;

        if (reason === "Validation error") {
            handleSaveValidationError(error);
            return;
        }
        if (detail && detail.ExceptionType &&
            detail.ExceptionType.indexOf('OptimisticConcurrencyException') !== -1) {
            // Concurrency error 
            reason =
                "Another user, perhaps the server, may have deleted one or all of the orders.";
            manager.rejectChanges();
        }

        logger.error(error,
            "Failed to save changes. " + reason +
            " You may have to restart the app.");
    }

    function saveFinished() { isSaving = false; }

    function handleSaveValidationError(error) {
        var message = "Not saved due to validation error";
        try { // fish out the first error
            var firstErr = error.entitiesWithErrors[0].entityAspect.getValidationErrors()[0];
            message += ": " + firstErr.errorMessage;
        } catch (e) { /* eat it for now */ }
        logger.error(message);
    }

    //#endregion generic

    return datacontext;

});